﻿namespace WMS6.Web
{
    using System;
    using System.Collections;
    using System.Runtime.InteropServices;
    using System.Web;
    using System.Xml;

    public sealed class FilterConfig
    {
        private string configXmlFileName = null;
        private static ArrayList filterMappings = new ArrayList();
        private static Hashtable filters = new Hashtable();

        private FilterConfig(string configXmlFileName)
        {
            this.configXmlFileName = configXmlFileName;
        }

        public static void DoFilter(HttpApplication application)
        {
            DoFilter(application, "filter-config.xml");
        }

        public static void DoFilter(HttpApplication application, string configFileName)
        {
            new FilterConfig(configFileName).DoFilterImpl(application);
        }

        private void DoFilterImpl(HttpApplication application)
        {
            string appPath = application.Request.ApplicationPath;
            string shortRequestPath = this.GetShortPath(appPath, application.Request.Path);
            if (filterMappings == null)
            {
                filterMappings = new ArrayList();
            }
            if (filters == null)
            {
                filters = new Hashtable();
            }
            if ((filterMappings.Count == 0) || (filters.Count == 0))
            {
                filterMappings.Clear();
                filters.Clear();
                this.ReadXml(application);
            }
            ArrayList filterList = new ArrayList();
            foreach (FilterMapping mapping in filterMappings)
            {
                if (this.IsAspXFileEqual(shortRequestPath, mapping.urlPattern))
                {
                    filterList.Add(filters[mapping.filterName]);
                }
            }
            if (filterList.Count != 0)
            {
                new FilterChain(application, filterList.GetEnumerator()).DoFilter();
            }
        }

        private string GetShortPath(string appPath, string path)
        {
            if (appPath.Length >= path.Length)
            {
                return string.Empty;
            }
            return path.Substring(appPath.Length, path.Length - appPath.Length);
        }

        private bool IsAspXFileEqual(string shortRequestPath, string configPath)
        {
            shortRequestPath = shortRequestPath.ToLower();
            configPath = configPath.ToLower();
            if (!shortRequestPath.StartsWith("/"))
            {
                shortRequestPath = "/" + shortRequestPath;
            }
            if (!configPath.StartsWith("/"))
            {
                configPath = "/" + configPath;
            }
            int index1 = shortRequestPath.LastIndexOf('/');
            int index2 = configPath.LastIndexOf('/');
            string path1 = shortRequestPath.Substring(0, index1);
            string path2 = configPath.Substring(0, index2);
            if (path1 != path2)
            {
                return false;
            }
            string aspx1 = shortRequestPath.Substring(index1, shortRequestPath.Length - path1.Length);
            string aspx2 = configPath.Substring(index2, configPath.Length - path2.Length);
            return ((aspx2 == "/*") || (aspx1 == aspx2));
        }

        private void ReadXml(HttpApplication application)
        {
            string virtualName = application.Request.PhysicalApplicationPath + '\\' + this.configXmlFileName;
            XmlDocument document = new XmlDocument();
            document.Load(virtualName);
            try
            {
                this.ReadXmlDocument(document);
            }
            catch
            {
                filters = null;
                filterMappings = null;
                throw;
            }
        }

        private void ReadXmlDocument(XmlDocument document)
        {
            XmlElement filterConfig = document.DocumentElement;
            if (filterConfig.Name != "filter-config")
            {
                throw new FilterConfigException("配置文件根元素必须是filter-config");
            }
            foreach (System.Xml.XmlNode node in filterConfig.ChildNodes)
            {
                XmlNodeList filterNameList;
                XmlElement filterNameNode;
                string filterName;
                XmlElement filterClassNode;
                if (node.NodeType != XmlNodeType.Element)
                {
                    continue;
                }
                XmlElement element = (XmlElement) node;
                if (element.Name == "filter")
                {
                    filterNameList = element.GetElementsByTagName("filter-name");
                    if (filterNameList.Count != 1)
                    {
                        throw new FilterConfigException("filter元素中必须且只能出现一次filter-name子元素");
                    }
                    filterNameNode = (XmlElement) filterNameList[0];
                    if ((filterNameNode.ChildNodes.Count != 1) || (filterNameNode.ChildNodes[0].NodeType != XmlNodeType.Text))
                    {
                        throw new FilterConfigException("filter-name元素必须拥有文本");
                    }
                    filterName = filterNameNode.ChildNodes[0].Value.Trim();
                    XmlNodeList filterClassList = element.GetElementsByTagName("filter-class");
                    if (filterClassList.Count != 1)
                    {
                        throw new FilterConfigException("filter元素中必须且只能出现一次filter-class子元素");
                    }
                    filterClassNode = (XmlElement) filterClassList[0];
                    if ((filterClassNode.ChildNodes.Count != 1) || (filterClassNode.ChildNodes[0].NodeType != XmlNodeType.Text))
                    {
                        throw new FilterConfigException("filter-class元素必须拥有文本");
                    }
                    string filterClass = filterClassNode.ChildNodes[0].Value.Trim();
                    if (filters.Contains(filterName))
                    {
                        throw new FilterConfigException("两个filter的filter-name冲突");
                    }
                    Type type = Type.GetType(filterClass);
                    if (type == null)
                    {
                        throw new FilterConfigException("filter-class指定的类型不存在");
                    }
                    if (!typeof(IFilter).IsAssignableFrom(type))
                    {
                        throw new FieldAccessException("filter-class指定的类没有实现IFilter接口");
                    }
                    try
                    {
                        IFilter filter = (IFilter) Activator.CreateInstance(type);
                        filters.Add(filterName, filter);
                    }
                    catch
                    {
                        throw new FieldAccessException("无法无参数实例化filter-class指定的类");
                    }
                }
                else
                {
                    if (!(element.Name == "filter-mapping"))
                    {
                        throw new FilterConfigException("根元素下只允许出现子元素只能是<filter>和<filter-mapping>");
                    }
                    filterNameList = element.GetElementsByTagName("filter-name");
                    if (filterNameList.Count != 1)
                    {
                        throw new FilterConfigException("filter-mapping元素中必须且只能出现一次filter-name子元素");
                    }
                    filterNameNode = (XmlElement) filterNameList[0];
                    if ((filterNameNode.ChildNodes.Count != 1) || (filterNameNode.ChildNodes[0].NodeType != XmlNodeType.Text))
                    {
                        throw new FilterConfigException("filter-name元素必须拥有文本");
                    }
                    filterName = filterNameNode.ChildNodes[0].Value.Trim();
                    if (!filters.Contains(filterName))
                    {
                        throw new FilterConfigException("filter-mapping元素中使用的filter-name不存在");
                    }
                    XmlNodeList urlPatternList = element.GetElementsByTagName("url-pattern");
                    if (urlPatternList.Count != 1)
                    {
                        throw new FilterConfigException("filter元素中必须且只能出现一次url-pattern子元素");
                    }
                    filterClassNode = (XmlElement) urlPatternList[0];
                    if ((filterClassNode.ChildNodes.Count != 1) || (filterClassNode.ChildNodes[0].NodeType != XmlNodeType.Text))
                    {
                        throw new FilterConfigException("url-pattern元素必须拥有文本");
                    }
                    string urlPattern = filterClassNode.ChildNodes[0].Value.Trim();
                    if ((urlPattern.Length == 0) || (urlPattern[0] != '/'))
                    {
                        throw new FilterConfigException("url-pattern元素的文本必须以/开头");
                    }
                    filterMappings.Add(new FilterMapping(filterName, urlPattern));
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct FilterMapping
        {
            public string filterName;
            public string urlPattern;
            public FilterMapping(string filterName, string urlPattern)
            {
                this.filterName = filterName;
                this.urlPattern = urlPattern;
            }
        }
    }
}

